package osami.communication;

import java.util.Vector;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;

import osami.bundle.OsamiBundle;
import osami.bundle.OsamiServiceReference;
import osami.bundle.ServiceEventListener;
import osami.communication.translate.FindServiceMessage;
import osami.communication.translate.FindServiceResponseMessage;
import osami.communication.translate.HandshakeMessage;
import osami.communication.translate.OsamiExceptionMessage;
import osami.communication.translate.RemoteCallMessage;
import osami.communication.translate.RemoteCallResponseMessage;
import osami.communication.translate.ServiceUpdateMessage;
import osami.communication.translate.TranslationException;


public class Activator implements BundleActivator {

	protected CommServer iCommServer;
	private Vector<OsamiServiceReference> iLocalServices = new Vector<OsamiServiceReference>();
	private Vector<ServiceReference> iRemoteServices = new Vector<ServiceReference>();
	private Vector<ServiceEventListener> iServiceEventListeners = new Vector<ServiceEventListener>();

	
	private OsamiIdentifier iIdentifier;
	private BundleContext iOsamiMainContext;

	private static final int SERVICE_EVENT_SERVICE_ADDED = 		0;
	private static final int SERVICE_EVENT_SERVICE_REMOVED =	1;
	

	/*
	 * (non-Javadoc)
	 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
	 */
	public void start(BundleContext context) throws Exception {
		System.out.println("OsamiServiceRelay");
		// create the communication server object
		iCommServer = new CommServer( this );

		// TODO: Dependency to Policy Manager 
		// Create few sample Communication policies, but start only one
		//CommunicationPolicy policy = new CommunicationPolicy( "TCPIPFIRST", "TCPIP_FIRST", "Communication", iCommServer );
		//iPolicyManager.addPolicy(policy);
		//iPolicyManager.startPolicy(policy);
		
		//policy = new CommunicationPolicy( "BTFIRST", "BLUETOOTH_FIRST", "Communication", iCommServer );
		//iPolicyManager.addPolicy(policy);
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
	 */
	public void stop(BundleContext context) throws Exception {
		System.out.println("ServiceRelayOut");
	}
	/**
	 * Construct and return a handshake object.
	 * 
	 * @return HandshakeMessage
	 * @throws InvalidSyntaxException 
	 */
	public HandshakeMessage getHandShake()
	{
		HandshakeMessage handshake = new HandshakeMessage();
		handshake.setSender( iIdentifier );
		
		// add supported translators to the message
		handshake.setTranslators( iCommServer.getTranslators() );
		
		return handshake;
	}
	
	/**
	 * Handles a service update command:
	 *  - adds new remote service references to neighbour services -list
	 *  - removes registered neighbour services
	 *  - adds a new neighbour node
	 *  - removes an existing neighbour node
	 *  - handles incoming service searches
	 * 
	 * @param aServiceUpdateMsg
	 */
	public void handleOsamiServiceUpdate( ServiceUpdateMessage aServiceUpdateMsg )
	{
		OsamiIdentifier senderID = aServiceUpdateMsg.getSender();
		System.out.println( "OsamiEngine.handleOsamiServiceUpdate: Got a service update message from " + senderID.getID() );

		int action = aServiceUpdateMsg.getAction();
		
		switch ( action )
		{
		case ServiceUpdateMessage.REMOVE_SERVICES:
		{
			// get services array from the update message
			for ( OsamiServiceReference service : aServiceUpdateMsg.getServices() )
				removeRemoteService( service );
		}break;
		case ServiceUpdateMessage.ADD_SERVICES:
		{
			// get services array from the update message
			for ( OsamiServiceReference service : aServiceUpdateMsg.getServices() )
				addRemoteService( service );
		}break;
		case ServiceUpdateMessage.REMOVE_NODE:
		{
			removeRemoteServices( senderID );
		}break;
		case ServiceUpdateMessage.ADD_NODE:
		{
			// get services array from the update message
			for ( OsamiServiceReference service : aServiceUpdateMsg.getServices() )
				addRemoteService( service );
		}break;
		default:
		{
			System.out.println( "OsamiEngine.handleOsamiServiceUpdate: WARNING: Unknown action: " + action );
		}break;
		}
		
		// EXECUTE BUNDLE RELATED POLICIES
		executeBundlePolicies();
		
	}
	
	/**
	 * Adds a remote service reference to the remote services -vector.
	 * 
	 * @param aReference
	 */
	public void addRemoteService( OsamiServiceReference aReference )
	{
		System.out.println( "OsamiEngine.addRemoteService: Adding service: " + aReference.getName() );

		if ( !iRemoteServices.contains( aReference ) )
		{
			System.out.println( "OsamiEngine.addRemoteService: service added." );
			iRemoteServices.add( aReference );
			notifyListeners( aReference, SERVICE_EVENT_SERVICE_ADDED );
		}
		else
		{
			// do nothing if service is already in the remote services vector
			System.out.println( "OsamiEngine.addRemoteService: service already found, skipping." );
		}
	}

	/**
	 * Notify all listeners that there are some changes.
	 * 
	 * @param aService
	 * @param aEvent
	 */
	public void notifyListeners( OsamiServiceReference aService, int aEvent )
	{
		switch( aEvent )
		{
		case SERVICE_EVENT_SERVICE_ADDED:
		{
			for ( int i = 0; i < iServiceEventListeners.size(); i++ )
				iServiceEventListeners.elementAt(i).serviceAdded( aService );
		}break;
		case SERVICE_EVENT_SERVICE_REMOVED:
		{
			for ( int i = 0; i < iServiceEventListeners.size(); i++ )
				iServiceEventListeners.elementAt(i).serviceRemoved( aService );
		}break;
		default:
		{
			System.out.println( "OsamiEngine.notifyListeners: ERROR: Unknown event: " + aEvent );
		}
		}
	}
	/**
	 * Updates serviceMessage.
	 * 
	 * @return ServiceUpdateMessage
	 */
	public ServiceUpdateMessage getServicesMessage()
	{
		ServiceUpdateMessage servicesMessage = new ServiceUpdateMessage();
		
		// add local services to the message
		servicesMessage.setSender( iIdentifier );
		servicesMessage.setServices( iLocalServices );
		servicesMessage.setAction( ServiceUpdateMessage.ADD_SERVICES );
		
		return servicesMessage;
	}
	
	/**
	 * Searches for required service locally and passes the message along.
	 * 
	 * @param aMessage
	 * @throws TranslationException
	 * @throws OsamiException
	 */
	@SuppressWarnings("unchecked")
	public void handleFindServiceMessage( FindServiceMessage aMessage ) throws TranslationException, OsamiException
	{
		System.out.println( "OsamiEngine.handleFindServiceMessage: handling a FindServiceMessage" );
		
		// get the name of the searched interface from the message
		String interfaceName = aMessage.getInterfaceName();
		
		// search for the interface name from local services
		for ( OsamiServiceReference reference : iLocalServices )
		{
			if ( reference.implementsInterface( interfaceName ) )
			{
				System.out.println( "OsamiEngine.handleFindServiceMessage: SERVICE_UPDATE_FIND_SERVICE: service found!" );
				
				// if service is found, create a "service found" -message
				// and return it to the sender
				FindServiceResponseMessage response = new FindServiceResponseMessage( aMessage.getCallNumber(), true );
				
				// add information about the service (the route is saved to the service reference)
				OsamiServiceReference returnedReference = new OsamiServiceReference( reference );
				Vector<OsamiIdentifier> route = (Vector<OsamiIdentifier>)aMessage.getCallPath().clone();

				// insert local id to the end of the call path
				route.add( iIdentifier );

				returnedReference.setRoute( route );
				response.setServiceReference( returnedReference );
				
				try
				{
					// send the message back to the sender
					iCommServer.send( response, aMessage.getSender() );
				}
				catch ( Exception exception )
				{
					exception.printStackTrace();
				}
				
				return;
			}
		}

		System.out.println( "OsamiEngine.handleFindServiceMessage: SERVICE_UPDATE_FIND_SERVICE: service was not found locally." );

		// store original values
		int originalCallNumber = aMessage.getCallNumber();
		OsamiIdentifier originalSender = aMessage.getSender();

		// insert local id to the callpath
		aMessage.getCallPath().add( iIdentifier );

		for ( Connection connection : iCommServer.getConnections() )
		{
			boolean sendMsg = true;
			
			for ( OsamiIdentifier id : aMessage.getCallPath() )
				if ( id.getID() == connection.getIdentifier().getID() )
				{
					sendMsg = false;
					break;
				}
			
			if ( sendMsg )
			{
				aMessage.setCallNumber( iCommServer.getUniqueCallNumber() );
				
				System.out.println( "OsamiEngine.handleFindServiceMessage: SERVICE_UPDATE_FIND_SERVICE: Sending the query to a neighbour..." );

				FindServiceResponseMessage response = (FindServiceResponseMessage)connection.send( aMessage );
				
				if ( response != null && response.isSuccess() )
				{
					System.out.println( "OsamiEngine.handleFindServiceMessage: SERVICE_UPDATE_FIND_SERVICE: A neighbour had the requested service!" );

					// add callers call number to the message
					response.setCallNumber( originalCallNumber );
					
					iCommServer.send( response, originalSender );
					return;
				}
			}
		}
		
		// at this point we know that none of this node's neighbours have the requested service,
		// so we can return a "not found" message.
		System.out.println( "OsamiEngine.handleFindServiceMessage: SERVICE_UPDATE_FIND_SERVICE: The service was not found from local node or it's neighbours, returning \"not found\" -reply to id " + originalSender.getID() );

		// add callers call number to the message
		FindServiceResponseMessage response = new FindServiceResponseMessage( originalCallNumber, false );
		iCommServer.send( response, originalSender );
	}
	
	/**
	 * Depending on the request, this method either executes a local bundle call
	 * or passes the request to another OSAMI bundle to be executed. 
	 * 
	 * @param aRequest
	 * @return RemoteCallResponseMessage
	 * @throws JSONException
	 * @throws OsamiException 
	 */
	public RemoteCallResponseMessage handleBundleCall( RemoteCallMessage aRequest ) throws TranslationException, OsamiException
	{
		Vector<OsamiIdentifier> route = aRequest.getRoute();
		int destinationIdentifier = route.get(0).getID();
		
		// is the destination identifier the same as the local node ID?
		if ( destinationIdentifier == iIdentifier.getID() && route.size() <= 1 )
		{
			// local call
			System.out.println( "OsamiEngine.handleBundleCall: executing a local bundle call..." );
			return executeLocalBundleCall( aRequest );
		}
		else
		{
			// do the remote call
			System.out.println( "OsamiEngine.handleBundleCall: sending the request to a remote node..." );

			// remove local id from the beginning of the call path
			// local id is in the beginning of the call path only when there's a proxy or several proxies
			// in the call path
			if ( destinationIdentifier == iIdentifier.getID() )
			{
				aRequest.popFirstFromRoute();
			}
			
			try
			{
				return (RemoteCallResponseMessage)iCommServer.send( aRequest );
			}
			catch ( OsamiException aException )
			{
				System.out.println( "OsamiEngine.handleBundleCall: Exception: " + aException.getMessage() );
				
				// send an exception message
				return new OsamiExceptionMessage( aException.getMessage() );
			}
		}
	}
	
	/**
	 * Finds a local bundle by the name described in the message and
	 * does a method call. 
	 * 
	 * @param aMessage
	 * @return RemoteCallResponseMessage
	 * @throws TranslationException 
	 */
	public RemoteCallResponseMessage executeLocalBundleCall( RemoteCallMessage aMessage ) throws TranslationException
	{
		System.out.println( "OsamiEngine.executeLocalBundleCall: RBC request received." );

		// fetch interface name
		String interfaceName = aMessage.getInterfaceName();
		
		// search for the bundle
		OsamiBundle service = null;
		try
		{
			// get a list of matching services
			ServiceReference refs[] = iOsamiMainContext.getServiceReferences( OsamiBundle.class.getName(), "(interface=" + interfaceName + ")" );

			// TODO: mitä tehdään jos löytyy useampi interfacen toteuttava luokka?
			if ( refs != null )
				service = (OsamiBundle)iOsamiMainContext.getService( refs[0] );
		}
		catch ( InvalidSyntaxException exception )
		{
			return new OsamiExceptionMessage( "ERROR: Invalid syntax exception: " + exception.getMessage() );
		}
		
		if ( service != null )
		{
			try
			{
				System.out.println( "OsamiEngine.executeLocalBundleCall: service was found, executing call." );
	
				// if a service was found, execute the call
				return service.remoteServiceCall( aMessage );
			}
			catch ( Exception aException )
			{
				aException.printStackTrace();
				return new OsamiExceptionMessage( "ERROR: Service was not found: " + aException.getMessage() );
			}
		}
		else
		{
			// matching service was not found
			System.out.println( "OsamiEngine.executeLocalBundleCall: ERROR: Service was not found." );
			return new OsamiExceptionMessage( "ERROR: Service was not found." );
		}
	}
	
	/**
	 * Executes Bundle Policies.
	 */
	private void executeBundlePolicies() {
		/*
		Vector<Policy> policies = iPolicyManager.getActivePolicies();
		for (Policy policy: policies) {
			if(policy.getType().contentEquals("Bundle"))
				policy.execute();
		}
		*/
	}
	
		/**
	 * Removes remote services from the remote services -vector based
	 * on the id of the remote OSAMI node.
	 * 
	 * @param aIdentifier
	 */
	public void removeRemoteServices( OsamiIdentifier aIdentifier )
	{
		/* TODO: Sync with OSGi R4.2  
		for ( int i = 0; i < iRemoteServices.size(); )
		{
			if ( iRemoteServices.elementAt(i).routeHas( aIdentifier ) )
			{
				System.out.println( "OsamiEngine.removeRemoteServices: Removing service: " + iRemoteServices.elementAt(i).getName() );
				notifyListeners( iRemoteServices.elementAt(i), SERVICE_EVENT_SERVICE_REMOVED );
				iRemoteServices.remove(i);
			}
			else
				i++;
		}
		*/
	}
	
	
	/**
	 * Removes remote services from the remote services -vector based
	 * on the id of the remote OSAMI node.
	 * 
	 * @param aReference
	 */
	public void removeRemoteService( OsamiServiceReference aReference )
	{
		/* TODO: OSGi spec R4.2
		System.out.println( "OsamiEngine.removeRemoteService: Removing service: " + aReference.getName() );

		for ( int i = 0; i < iRemoteServices.size(); )
		{
			if ( iRemoteServices.elementAt(i).equals( aReference ) )
			{
				notifyListeners( iRemoteServices.elementAt(i), SERVICE_EVENT_SERVICE_REMOVED );
				iRemoteServices.remove(i);
				return;
			}
		}
		*/
	}


}
